package cn.lingyangwl.agile.gateway.service.impl;

import cn.lingyangwl.agile.common.core.exception.enums.*;
import cn.lingyangwl.agile.gateway.exception.*;
import cn.lingyangwl.agile.gateway.model.ram.*;
import cn.lingyangwl.agile.gateway.remote.*;
import cn.lingyangwl.agile.gateway.service.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.auth.*;
import cn.lingyangwl.framework.tool.core.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {

    private static final PathMatcher pathMatcher = new AntPathMatcher();
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private AuthApi authApi;

    /**
     * key: serviceCode - 一般请求路径第一段
     * value: moduleCode
     */
    private static final Map<String, Set<String>> resourceModuleMap = new ConcurrentHashMap<>();

    /**
     * oauth2 客户端权限
     */
    private static final Map<String, Map<String, List<String>>> clientPermissionMap = new ConcurrentHashMap<>();

    /**
     * 是否有权限访问当前路径
     * @param loginUser 当前登录用户
     */
    @Override
    public void hasPermission(ServerWebExchange exchange, LoginUser loginUser, String uri) {
        String tenantId = String.valueOf(loginUser.getTenant().getTenantId());
        StopWatch stopWatch = StopWatch.createStarted();
        boolean hasPerm = false;
        try {
            // 后台管理员和租户管理员(权限只针对所管理的租户)
            if (loginUser.getIsTenantOwner()) {
                return;
            }

            Long userId = loginUser.getUserId();

            uri = uri.replaceFirst("/", "");
            // 去掉服务名的请求uri
            String serviceUri = UrlUtils.removeEndSlash(UrlUtils.removeRepeatSlashOfUrl(uri.substring(uri.indexOf("/"))));

            // 获取服务名
            String serviceCode = uri.substring(0, uri.indexOf("/"));
            Set<String> moduleCodes = resourceModuleMap.get(serviceCode);
            if (CollectionUtils.isEmpty(moduleCodes)) {
                throw new BizException("请联系管理员到控制台配置资源模块, serviceCode: {}", serviceCode);
            }

            hasPerm = checkClientPermission(serviceCode, serviceUri, loginUser);
            if (hasPerm) {
                return;
            }

            // 获取缓存中获取用户权限, 精确定位具体服务的缓存, 避免了由于用户权限数据过多导致缓存查询命令执行超时
            String key = String.format(OAuth2Cons.CACHE_USER_AUTHORITY, userId, tenantId);
            List<UserAuthority> userAuthority = redisTemplate.opsForHash().multiGet(key, new ArrayList<>(moduleCodes))
                    .stream().filter(e -> Objects.nonNull(e) && e instanceof UserAuthority)
                    .map(e -> (UserAuthority) e).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(userAuthority)) {
                log.info("access denied, tenantId: {}, userId: {}", tenantId, userId);
                throw new AccessDeniedException(GlobalErrorEnum.ACCESS_DENIED);
            }

            hasPerm = userAuthority.stream()
                    .flatMap(e -> e.getResourceUrls().stream())
                    .anyMatch(url -> pathMatcher.match(url, serviceUri));

            if (!hasPerm) {
                log.info("access denied, tenantId: {}, userId: {}", tenantId, userId);
                throw new AccessDeniedException(GlobalErrorEnum.ACCESS_DENIED);
            }
        } finally {
            long time = stopWatch.getTime(TimeUnit.MILLISECONDS);
            stopWatch.stop();
            log.info("hasPermission - userName: {}, time: {} ms, hasPerm: {}, url: {}", loginUser.getUserName(), time, hasPerm, uri);
        }
    }

    /**
     * 判断指定的客户端是否有访问当前路径权限
     * @return true 有权限, false 无权限
     */
    public boolean checkClientPermission(String serviceCode, String serviceUri, LoginUser loginUser) {
        // 先从客户端中判断是否有权限, 如果有直接返回
        String clientId = loginUser.getClientId();
        Map<String, List<String>> clientPermission = clientPermissionMap.get(clientId);
        if (Objects.isNull(clientPermission)) {
            synchronized (clientId.intern()) {
                clientPermission = clientPermissionMap.get(clientId);
                if (Objects.isNull(clientPermission)) {
                    // 调用远程服务获取权限
                    try {
                        clientPermission = authApi.listClientPermission(clientId).get();
                    } catch (Exception e) {
                        log.error("error: ", e);
                        throw new BizException("授权服务已下线");
                    }
                    clientPermissionMap.put(clientId, clientPermission);
                }
            }
        }
        return clientPermission.getOrDefault(serviceCode, Collections.emptyList())
                .stream().anyMatch(url -> pathMatcher.match(url, serviceUri));
    }

    @Override
    public void updateResourceModule(List<RamModule> req) {
        Map<String, Set<String>> map = req.stream().filter(e -> StringUtils.isNotEmpty(e.getServiceCode()))
                .collect(Collectors.groupingBy(
                        RamModule::getServiceCode, Collectors.mapping(RamModule::getCode, Collectors.toSet()))
                );
        resourceModuleMap.clear();
        resourceModuleMap.putAll(map);
    }

    @Override
    public void deleteClientAuthority(String clientId) {
        clientPermissionMap.remove(clientId);
    }
}
